package com.dxy.lc.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.dxy.lc.base.StatusMessageEnum;
import com.dxy.lc.base.UnifiedException;
import com.dxy.lc.model.entity.Attachment;
import com.dxy.lc.mapper.AttachmentMapper;
import com.dxy.lc.model.bean.FileUploadDto;
import com.dxy.lc.service.AttachmentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.web.multipart.MultipartFile;

import javax.activation.MimetypesFileTypeMap;

/**
 * <p>
 * 附件表 服务实现类
 * </p>
 *
 * @author dxy
 * @since 2024-09-28
 */
@Slf4j
@Service
public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachment> implements AttachmentService {

    private String baseDir;

    @Value("${upload.conf.base-dir:}")
    public void setBaseDir(String baseDir) {
        if (StrUtil.isBlank(baseDir)) {
            throw new IllegalArgumentException("请配置文件存储基础目录");
        }
        if (!baseDir.endsWith("/")) {
            baseDir = baseDir + "/";
        }
        // String absolutePath = FileUtil.getAbsolutePath(baseDir);
        // File baseDirFile = FileUtil.mkdir(absolutePath);
        // this.baseDir = baseDirFile.getAbsolutePath();// 这样写是没有/结尾的
        this.baseDir = baseDir;
        FileUtil.mkdir(this.baseDir);
        log.info("已初始化附件存储目录：{}", this.baseDir);
    }


    @Override
    public void delByIds(List<Integer> ids) {
        for (Integer id : ids) {
            Attachment Attachment = getById(id);
            if (Attachment != null) {
                // 从文件系统中删除
                File attachment = new File(getAbsoluteFilePath(Attachment.getFilePath()));
                attachment.delete();
                // 删除数据库中的附件记录
                removeById(id);
            }
        }
    }

    @Override
    public Attachment getDetail(Integer id) {
        Attachment attachment = baseMapper.selectById(id);
        setUrl(attachment);
        return attachment;
    }


    @Override
    public List<Attachment> batchUpload(List<FileUploadDto> dtoList) {
        List<Attachment> list = new ArrayList<>();
        for (FileUploadDto dto : dtoList) {
            // 判断附件是否为空
            if (dto == null || dto.getFile() == null || dto.getFile().isEmpty()) {
                throw new IllegalArgumentException("附件不能为空！");
            }
            try {
                // 原始附件名
                String originalFilename = StrUtil.isBlank(dto.getFileName()) ? dto.getFile().getOriginalFilename() : dto.getFileName();
                // 扩展名
                String extName = FileNameUtil.extName(originalFilename);
                // 新文件名
                String newFileName = IdUtil.fastSimpleUUID() + (StrUtil.isBlank(extName) ? "" : "." + extName);
                // 相对路径
                LocalDateTime now = LocalDateTimeUtil.now();
                String nowDateDirPath = "/" + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
                String storagePath = nowDateDirPath + newFileName;
                // 进行上传
                File dest = doUpload(dto.getFile(), storagePath);
                // 保存到数据库
                Attachment attachment = new Attachment();
                attachment.setBizType(dto.getBizType());
                attachment.setBizId(dto.getBizId());
                attachment.setFileType(extName);
                attachment.setFileName(originalFilename);
                attachment.setFileSize(dest.length());
                attachment.setFilePath(storagePath);
                baseMapper.insert(attachment);

                // 设置预览和下载url
                setUrl(attachment);

                list.add(attachment);
            } catch (IOException e) {
                log.error("文件上传失败: ", e);
                throw new UnifiedException(StatusMessageEnum.FAIL);
            }
        }
        return list;
    }

    @Override
    public ResponseEntity<Resource> getFile(String type, Long id) {
        // 从数据库获取附件记录
        Attachment attachment = getById(id);
        if (attachment == null) {
            log.error("文件{}不存在", id);
            return ResponseEntity.notFound().build();
        }

        FileSystemResource fileSystemResource = new FileSystemResource(getAbsoluteFilePath(attachment.getFilePath()));
        if (!fileSystemResource.exists()) {
            log.error("文件{}不存在", id);
            return ResponseEntity.notFound().build();
        }

        String fileName = attachment.getFileName();
        // response.setHeader("Content-disposition", "attachment;filename=" +filename);
        // Content-disposition: 有两个选项：
        //   1、inline表示在页面中打开查看预览，
        //   2、attachment表示以附件方式下载（弹出下载框）
        // 注意:filename如果是中文会出现乱码：解决办法：
        // 　　1、将filename 替换为 new String(filename.getBytes(), "ISO8859-1");
        // 　　2、将filename 替换为 URLEncoder.encode(filename, "utf-8");
        // 原文链接：https://blog.csdn.net/qq_45736175/article/details/108326146
        ContentDisposition disposition = ContentDisposition.builder(type).filename(fileName, StandardCharsets.UTF_8).build();
        HttpHeaders httpHeaders = new HttpHeaders();
        String contentType = getContentType(fileSystemResource.getFile());
        httpHeaders.setContentType(StrUtil.isBlank(contentType) ? MediaType.APPLICATION_OCTET_STREAM : MediaType.parseMediaType(contentType));
        httpHeaders.setContentDisposition(disposition);
        httpHeaders.setCacheControl(CacheControl.noCache());
        return ResponseEntity.ok()
                .headers(httpHeaders)
                .contentLength(fileSystemResource.getFile().length())
                .body(fileSystemResource);
    }

    @Override
    public List<Attachment> getFileList(String bizType, String bizId) {
        List<Attachment> list = baseMapper.selectList(new LambdaQueryWrapper<Attachment>()
                .eq(StrUtil.isNotEmpty(bizType), Attachment::getBizType, bizType)
                .eq(StrUtil.isNotEmpty(bizId), Attachment::getBizId, bizId)
                .orderByDesc(Attachment::getCreatedAt));
        list.forEach(this::setUrl);
        return list;
    }

    @Override
    public void delByBizTypeAndBizId(String bizType, String bizId) {
        Assert.notEmpty(bizType, "bizType不能为空");
        Assert.notEmpty(bizId, "bizId不能为空");
        remove(new LambdaQueryWrapper<Attachment>()
                .eq(Attachment::getBizType, bizType)
                .eq(Attachment::getBizId, bizId));
    }

    private File doUpload(MultipartFile multipartFile, String storagePath) throws IOException {
        // 绝对路径
        String storePath = getAbsoluteFilePath(storagePath);
        FileUtil.mkParentDirs(storePath);
        // 上传
        File dest = new File(storePath);
        multipartFile.transferTo(dest);
        return dest;
    }

    private String getAbsoluteFilePath(String relativePath) {
        Assert.notBlank(relativePath);
        String basePath = this.baseDir;
        if (!basePath.endsWith("/")) {
            basePath = basePath + "/";
        }
        if (relativePath.startsWith("/")) {
            relativePath = relativePath.replaceFirst("/", "");
        }
        return basePath + relativePath;
    }


    private String getContentType(File file) {
        Assert.notNull(file);
        // 利用nio提供的类判断文件ContentType
        String contentType = null;
        try {
            contentType = Files.probeContentType(file.toPath());
        } catch (IOException e) {
            // do nothing
        }
        // 若失败则调用另一个方法进行判断
        if (contentType == null) {
            contentType = new MimetypesFileTypeMap().getContentType(file);
        }
        log.debug("contentType：{}", contentType);
        return contentType;
    }

    private void setUrl(Attachment attachment) {
        if (attachment == null || attachment.getId() == null) {
            return;
        }
        attachment.setPreviewUrl("/lc/attachment/get/inline/" + attachment.getId());
        attachment.setDownloadUrl("/lc/attachment/get/attachment/" + attachment.getId());
    }

}
